#include "esp_event.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "nvs_flash.h"
#include <string.h>
#include <sys/param.h>

#include "libtelnet.h"
#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include <lwip/netdb.h>

#define TELNETY_PORT 8888
#define KEEPALIVE_IDLE 5
#define KEEPALIVE_INTERVAL 5
#define KEEPALIVE_COUNT 3

#define CONFIG_EXAMPLE_IPV4

#define TAG "telnet"
#define MAX_USERS 3
char buffer[512];
static const telnet_telopt_t telopts[] = {{TELNET_TELOPT_ECHO, TELNET_WILL, TELNET_DO},
                                          {TELNET_TELOPT_NAWS, TELNET_WILL, TELNET_DO},
                                          {TELNET_TELOPT_TTYPE, TELNET_WILL, TELNET_DO},
                                          {-1, 0, 0}};

typedef struct {
    char *cli_name;
    char *(*cmd_run)(const char *buf);
} cmd_handle_t;

struct user_t {
    char name[32];
    char ip[128];
    int sock;
    char curr_dir[32];
    telnet_t *telnet;
    char linebuf[256];
    int linepos;
};

char *cmd_help(const char *buf) { return "help"; }

cmd_handle_t cmds_handle[] = {{
                                  .cli_name = "help",
                                  .cmd_run = cmd_help,
                              },
                              {
                                  .cli_name = "help",
                                  .cmd_run = cmd_help,
                              }};

static struct user_t users[MAX_USERS];

static void _message(const char *from, const char *msg) {
    int i;
    for (i = 0; i != MAX_USERS; ++i) {
        if (users[i].sock != -1) {
            telnet_printf(users[i].telnet, "%s: %s\n", from, msg);
        }
    }
}

// 1:匹配,0:不匹配
static int cmd_cmp(char *curr, char *check) {
    int i = 0;
    while (check[i] != 0) {
        if (curr[i] != check[i]) {
            return 0;
        }
        i++;
    }
    if (check[i] == 0 && (curr[i] == 0 || curr[i] == ' ')) {
        return 1;
    } else {
        return 0;
    }
}

/* process input line */
static void _online(const char *line, size_t overflow, void *ud) {
    struct user_t *user = (struct user_t *)ud;

    (void)overflow;

    /* if the user has no name, this is his "login" */
    if (user->name[0] == 0) {
        /* must not be empty, must be at least 32 chars */
        if (strlen(line) == 0 || strlen(line) > 32) {
            telnet_printf(user->telnet, "\r\nInvalid name,req <32 chars and not empty.\nwifi_log login:");
            return;
        }
        snprintf(user->name, sizeof(user->name), "%s", line);
        telnet_printf(user->telnet, "\r\n%s:%s$ ", user->name, user->curr_dir);
        return;
    }

    /* if line is "quit" then, well, quit */
    if (strcmp(line, "quit") == 0) {
        close(user->sock);
        user->sock = -1;
        telnet_printf(user->telnet, "\r\n%s", "** HAS QUIT **");
        user->name[0] = 0;
        telnet_free(user->telnet);
        return;
    } else if (strcmp(line, "clear") == 0) {
        char buf[] = {0x1B, 0x5B, 0x32, 0x4A, 0x1B, 0x5B, 0x48, 0, 0, 0};
        telnet_send(user->telnet, buf, sizeof(buf));
    } else {
        for (int i = 0; i < sizeof(cmds_handle) / sizeof(cmds_handle[0]); i++) {
            if (cmd_cmp((char *)line, (char *)cmds_handle->cli_name)) {
                char *dat = cmds_handle->cmd_run(line);
                if (dat) {
                    telnet_printf(user->telnet, "\r\n%s", dat);
                    break;
                }
            }
        }
    }
    telnet_printf(user->telnet, "\r\n%s:%s$ ", user->name, user->curr_dir);
}

static void linebuffer_push(telnet_t *telnet, char *buffer, size_t size, int *linepos, char ch, void *ud) {
    if (ch == '\r') {
        buffer[*linepos] = 0;
        _online(buffer, 0, ud);
        *linepos = 0;
    } else if (ch == 0x7f) {
        if ((*linepos) > 0) {
            (*linepos)--;
            char buf[7] = {0x08, 0x1b, 0x5b, 0x4b, 0, 0, 0};
            telnet_send(telnet, buf, sizeof(buf));
        }
    } else if (ch == 0) {
    } else {
        if (*linepos < (int)size) {
            buffer[(*linepos)++] = ch;
            telnet_send_text(telnet, &ch, 1);
        } else {
            buffer[size - 1] = 0;
            _online(buffer, size - 1, ud);
            *linepos = 0;
        }
    }
}

static void _send(int sock, const char *buffer, size_t size) {
    int rs;

    /* ignore on invalid socket */
    if (sock == -1)
        return;

    /* send data */
    while (size > 0) {
        if ((rs = send(sock, buffer, (int)size, 0)) == -1) {
            if (errno != EINTR && errno != ECONNRESET) {
                fprintf(stderr, "send() failed: %s\n", strerror(errno));
                exit(1);
            } else {
                return;
            }
        } else if (rs == 0) {
            fprintf(stderr, "send() unexpectedly returned 0\n");
            exit(1);
        }

        /* update pointer and size to see if we've got more to send */
        buffer += rs;
        size -= rs;
    }
}

static void _input(struct user_t *user, const char *buffer, size_t size) {
    unsigned int i;
    for (i = 0; user->sock != -1 && i != size; ++i)
        linebuffer_push(user->telnet, user->linebuf, sizeof(user->linebuf), &user->linepos, (char)buffer[i], user);
}

// static void do_retransmit(const int sock) {
//     int len;
//     char rx_buffer[128];

//     do {
//         len = recv(sock, rx_buffer, sizeof(rx_buffer) - 1, 0);
//         if (len < 0) {
//             ESP_LOGE(TAG, "Error occurred during receiving: errno %d", errno);
//         } else if (len == 0) {
//             ESP_LOGW(TAG, "Connection closed");
//         } else {
//             rx_buffer[len] = 0; // Null-terminate whatever is received and treat it like a string
//             ESP_LOGI(TAG, "Received %d bytes: %s", len, rx_buffer);

//             // send() can return less bytes than supplied length.
//             // Walk-around for robust implementation.
//             int to_write = len;
//             while (to_write > 0) {
//                 int written = send(sock, rx_buffer + (len - to_write), to_write, 0);
//                 if (written < 0) {
//                     ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
//                     // Failed to retransmit, giving up
//                     return;
//                 }
//                 to_write -= written;
//             }
//         }
//     } while (len > 0);
// }

static void _event_handler(telnet_t *telnet, telnet_event_t *ev, void *user_data) {
    struct user_t *user = (struct user_t *)user_data;
    switch (ev->type) {
    /* data received */
    case TELNET_EV_DATA:
        _input(user, ev->data.buffer, ev->data.size);
        break;
    /* data must be sent */
    case TELNET_EV_SEND:
        _send(user->sock, ev->data.buffer, ev->data.size);
        break;
    /* enable compress2 if accepted by client */
    case TELNET_EV_DO:
        break;
    case TELNET_EV_WILL:
        break;
    /* error */
    case TELNET_EV_ERROR:
        close(user->sock);
        user->sock = -1;
        if (user->name[0] != 0) {
            _message(user->name, "** HAS HAD AN ERROR **");
            user->name[0] = 0;
        }
        telnet_free(user->telnet);
        break;
    default:
        printf("%s:%d ignore:%d\n", __FILE__, __LINE__, ev->type);
        /* ignore */
        break;
    }
}

void telnet_server_task(void *pvParameters) {
    int addr_family = AF_INET;
    int ip_protocol = 0;
    int keepAlive = 1;
    int keepIdle = KEEPALIVE_IDLE;
    int keepInterval = KEEPALIVE_INTERVAL;
    int keepCount = KEEPALIVE_COUNT;
    int rs;
    int i;
    struct sockaddr_storage dest_addr;
    struct pollfd pfd[MAX_USERS + 1];

    memset(&pfd, 0, sizeof(pfd));
    memset(users, 0, sizeof(users));
    for (i = 0; i != MAX_USERS; ++i)
        users[i].sock = -1;

    if (addr_family == AF_INET) {
        struct sockaddr_in *dest_addr_ip4 = (struct sockaddr_in *)&dest_addr;
        dest_addr_ip4->sin_addr.s_addr = htonl(INADDR_ANY);
        dest_addr_ip4->sin_family = AF_INET;
        dest_addr_ip4->sin_port = htons(TELNETY_PORT);
        ip_protocol = IPPROTO_IP;
    }

    int listen_sock = socket(addr_family, SOCK_STREAM, ip_protocol);
    if (listen_sock < 0) {
        ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
        vTaskDelete(NULL);
        return;
    }
    int opt = 1;
    setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    ESP_LOGI(TAG, "Socket created");

    int err = bind(listen_sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
    if (err != 0) {
        ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
        ESP_LOGE(TAG, "IPPROTO: %d", addr_family);
        goto CLEAN_UP;
    }
    ESP_LOGI(TAG, "Socket bound, port %d", TELNETY_PORT);

    err = listen(listen_sock, 1);
    if (err != 0) {
        ESP_LOGE(TAG, "Error occurred during listen: errno %d", errno);
        goto CLEAN_UP;
    }

    /* initialize listening descriptors */
    pfd[MAX_USERS].fd = listen_sock;
    pfd[MAX_USERS].events = POLLIN;

    while (1) {
        for (i = 0; i != MAX_USERS; ++i) {
            if (users[i].sock != -1) {
                pfd[i].fd = users[i].sock;
                pfd[i].events = POLLIN;
            } else {
                pfd[i].fd = -1;
                pfd[i].events = 0;
            }
        }

        /* poll */
        rs = poll(pfd, MAX_USERS + 1, -1);
        if (rs == -1 && errno != EINTR) {
            fprintf(stderr, "poll() failed: %s\n", strerror(errno));
            close(listen_sock);
            return;
        }

        /* new connection */
        if (pfd[MAX_USERS].revents & (POLLIN | POLLERR | POLLHUP)) {
            /* acept the sock */
            struct sockaddr_storage source_addr;
            socklen_t addr_len = sizeof(source_addr);
            int client_sock = accept(listen_sock, (struct sockaddr *)&source_addr, &addr_len);
            if (client_sock < 0) {
                ESP_LOGE(TAG, "Unable to accept connection: errno %d", errno);
                return;
            }
            printf("Connection received.\n");

            /* find a free user */
            for (i = 0; i != MAX_USERS; ++i)
                if (users[i].sock == -1)
                    break;
            if (i == MAX_USERS) {
                printf("  rejected (too many users)\n");
                _send(client_sock, "Too many users.\r\n", 14);
                close(client_sock);
            }

                    // Set tcp keepalive option
            setsockopt(client_sock, SOL_SOCKET, SO_KEEPALIVE, &keepAlive, sizeof(int));
            setsockopt(client_sock, IPPROTO_TCP, TCP_KEEPIDLE, &keepIdle, sizeof(int));
            setsockopt(client_sock, IPPROTO_TCP, TCP_KEEPINTVL, &keepInterval, sizeof(int));
            setsockopt(client_sock, IPPROTO_TCP, TCP_KEEPCNT, &keepCount, sizeof(int));
            // Convert ip address to string
            if (source_addr.ss_family == PF_INET) {
                inet_ntoa_r(((struct sockaddr_in *)&source_addr)->sin_addr, users[i].ip, sizeof(users[i].ip) - 1);
            }
            ESP_LOGI(TAG, "Socket accepted ip address: %s", users[i].ip);

            /* init, welcome */
            users[i].sock = client_sock;
            users[i].name[0] = 0;
            snprintf(users[i].curr_dir, sizeof(users[i].curr_dir), "%s", "/");
            users[i].telnet = telnet_init(telopts, _event_handler, 0, &users[i]);
            telnet_printf(users[i].telnet, "wifi_log login:");
        }

        for (i = 0; i != MAX_USERS; ++i) {
            /* skip users that aren't actually connected */
            if (users[i].sock == -1)
                continue;

            if (pfd[i].revents & (POLLIN | POLLERR | POLLHUP)) {
                if ((rs = recv(users[i].sock, buffer, sizeof(buffer), 0)) > 0) {
                    telnet_recv(users[i].telnet, buffer, rs);
                } else if (rs == 0) {
                    printf("Connection closed.\n");
                    close(users[i].sock);
                    users[i].sock = -1;
                    if (users[i].name[0] != 0) {
                        _message(users[i].name, "** HAS DISCONNECTED **");
                        users[i].name[0] = 0;
                    }
                    telnet_free(users[i].telnet);
                } else if (errno != EINTR) {
                    fprintf(stderr, "recv(client) failed: %s\n", strerror(errno));
                    exit(1);
                }
            }
        }

        #if 0
        ESP_LOGI(TAG, "Socket listening");

        struct sockaddr_storage source_addr; // Large enough for both IPv4 or IPv6
        socklen_t addr_len = sizeof(source_addr);
        int sock = accept(listen_sock, (struct sockaddr *)&source_addr, &addr_len);
        if (sock < 0) {
            ESP_LOGE(TAG, "Unable to accept connection: errno %d", errno);
            break;
        }

        // Set tcp keepalive option
        setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &keepAlive, sizeof(int));
        setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, &keepIdle, sizeof(int));
        setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, &keepInterval, sizeof(int));
        setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, &keepCount, sizeof(int));
        // Convert ip address to string
        if (source_addr.ss_family == PF_INET) {
            inet_ntoa_r(((struct sockaddr_in *)&source_addr)->sin_addr, addr_str, sizeof(addr_str) - 1);
        }
        ESP_LOGI(TAG, "Socket accepted ip address: %s", addr_str);

        do_retransmit(sock);
        #endif
    }
    // shutdown(sock, 0);
    // close(sock);

CLEAN_UP:
    close(listen_sock);
    vTaskDelete(NULL);
}